package com.globallogic.javatrainings.sergiiivanov.model;

import java.io.Serializable;

import com.globallogic.javatrainings.sergiiivanov.resources.ErrorCodes;
import com.globallogic.javatrainings.sergiiivanov.resources.Messages;

/**
 * Stores information about lecture. Used by {@link Course} and has links to
 * {@link Room} and {@link Course}.<br>
 * 
 * @author Sergey
 * 
 */
public class Lecture implements Serializable, MyArrayToNamesCovertable {

    /**
     * Types of {@link Lecture} <li> {@link #LECTURE} <li> {@link #PRACTICE} <li>
     * {@link #EXAMINATION}
     * 
     * @author Sergey
     * 
     */
    public enum LectureType {
        /**
         * Lecture requires {@link Room} of RoomType.AUDITORIUM type
         */
        LECTURE(Messages.LECTURE_TYPE_LECTURE),
        /**
         * Practice required {@link Room} of RoomType.LABORATORY type
         */
        PRACTICE(Messages.LECTURE_TYPE_PRACTICE),
        /**
         * Test has no specific requirements and is optional lecture type<br>
         * Teachers could add tests into course on their own will without any
         * limits
         */
        TEST(Messages.LECTURE_TYPE_TEST),
        /**
         * Examination required {@link Room} of RoomType.LABORATORY or
         * RoomType.AUDITORIUM type
         */
        EXAMINATION(Messages.LECTURE_TYPE_EXAMINATION);
        /**
         * Returns list of all types names<br>
         * Used to display list on GUI
         * 
         * @return all types {@link #getRepresentation()} as String[]
         */
        public static String[] getNamesList() {
            LectureType[] types = values();
            String[] result = new String[types.length];
            for (int i = 0; i < types.length; i++) {
                result[i] = types[i].getRepresentation();
            }
            return result;
        }

        private String representation;

        private LectureType(String shownAs) {
            this.representation = shownAs;
        }

        /**
         * Returns String representation of the type<br>
         * See {@link Messages}.LECTURE_TYPE_... values
         * 
         * @return String representation as it defined in {@link Messages}
         *         .LECTURE_TYPE_...
         */
        public String getRepresentation() {
            return representation;
        }
    }

    private static final long serialVersionUID = 1L;
    private String name;
    private String description;
    private LectureType lectureType;
    private Room appointedRoom;
    private Course course;

    /**
     * Creates new empty lecture.<br>
     * Used by {@link Course#addLecture(String, Lecture.LectureType, String)}
     */
    public Lecture() {}

    /**
     * Quick way to create new {@link Lecture} with provided parameters.<br>
     * Name is set by calling {@link #setName(String)}<br>
     * Type is set by calling {@link #setType(LectureType)}
     * 
     * @param newName
     *            - new valid name (see {@link #setName(String)} )
     * @param type
     *            - {@link LectureType}
     * @throws UniversityWarning
     *             when name is not valid (see {@link #setName(String)} )
     */
    public Lecture(String newName, LectureType type) throws UniversityWarning {
        setName(newName);
        setType(type);
    }

    /**
     * Calculate available places for new students<br>
     * Used by {@link Course#availablePlaces()}<br>
     * {@link #availablePlaces()} = {@link Room#getCapacity()}<br>
     * If room is not appointed then returns {@link Integer#MAX_VALUE}
     * 
     * @return {@link Room#getCapacity()} or {@link Integer#MAX_VALUE}
     */
    public int availablePlaces() {
        if (course == null || appointedRoom == null) {
            return Integer.MAX_VALUE;
        }
        return appointedRoom.getCapacity() - course.countStudents() - 1;
    }

    /**
     * Compares room capacity to number of enrolled students.
     * 
     * @param r
     *            - Room to compare
     * @return - true, when room.capacity is fine (not less than number of
     *         enrolled students); false, when room==null or capacity is less
     *         than number of enrolled students
     */
    public boolean checkRoomSize(Room r) {
        return (r != null && r.getCapacity() >= (course.countStudents()+1) );
    }

    /**
     * Validates type of room before appointing.<br>
     * Used by {@link #setRoom(Room)} and {@link #setType(LectureType)}<br>
     * Validation is <b>true</b> when:<br>
     * <li>room or typeLecture is null <li>provided pair are RoomType.AUDITORIUM
     * and {@link LectureType#LECTURE} <li>provided pair are RoomType.LABORATORY
     * and {@link LectureType#PRACTICE} <li>provided pair are
     * RoomType.LABORATORY and {@link LectureType#EXAMINATION} <li>provided pair
     * are RoomType.AUDITORIUM and {@link LectureType#EXAMINATION} <li>provided
     * pair are any RoomType and {@link LectureType#TEST}
     * 
     * @param room
     *            {@link Room} to check
     * @param typeLecture
     *            {@link LectureType} to check
     * @return validation result
     */
    public boolean checkRoomType(Room room, LectureType typeLecture) {
        boolean result = false;
        if(room != null && lectureType != null){
            boolean lect$Aud = lectureType.equals(LectureType.LECTURE) && room.isAuditorium();
            boolean pract$Lab = lectureType.equals(LectureType.PRACTICE) && room.isLaboratory();
            boolean test_Exam$Aud_Lab = 
                    (lectureType.equals(LectureType.EXAMINATION) || lectureType.equals(LectureType.TEST) )&&
                    (room.isAuditorium() || room.isLaboratory());
            result = lect$Aud || pract$Lab || test_Exam$Aud_Lab;  
        }else{
            result = true;
            }
        return result;
    }

    /**
     * @return {@link Course} set by {@link #setCourse(Course)}
     */
    public Course getCourse() {
        return course;
    }

    /**
     * Returns name of the course set by {@link #setCourse(Course)} (null safe)<br>
     * Used by building management part of GUI
     * 
     * @return {@link Course#getName()} or {@link Const#STRING_EMPTY} if course
     *         is not set
     */
    public String getCourseName() {
        if (course == null)
            return Const.STRING_EMPTY;
        return course.getName();
    }

    /**
     * Returns description as it was set by {@link #setDescription(String)}
     * 
     * @return String or <b>null</b>
     */
    public String getDescription() {
        return description;
    }

    /**
     * Returns lecture type as it was set by {@link #setType(LectureType)}
     * 
     * @return {@link LectureType} or <b>null</b>
     */
    public LectureType getLectureType() {
        return lectureType;
    }

    /**
     * Returns name as it was set by {@link #setName(String)}<br>
     * Part of {@link MyArrayToNamesCovertable} mechanics
     * 
     * @return String or <b>null</b>
     */
    @Override
    public String getName() {
        return name;
    }

    /**
     * Returns {@link #setRoom(Room) appointed} room name as it is returned by
     * {@link Room#getName()} (null safe)
     * 
     * @return {@link Room#getName()} or {@link Const#STRING_EMPTY}
     */
    public String getRoomName() {
        return appointedRoom == null ? Const.STRING_EMPTY : appointedRoom.getName();
    }

    /**
     * Sets description as provided
     * 
     * @param newDescription
     *            String or <b>null</b>
     */
    public void setDescription(String newDescription) {
        this.description = newDescription;
    }

    /**
     * Sets name if valid value provided - not null, not empty, unique within
     * course set by {@link #setCourse(Course)}<br>
     * Used by {@link Course#addLecture(String, Lecture.LectureType, String)} ,
     * {@link #Lecture(String, LectureType)}
     * 
     * @param newName
     *            - valid name
     * @throws UniversityWarning
     *             when name is not valid
     */
    public void setName(String newName) throws UniversityWarning {
        if (newName == null || Const.STRING_EMPTY.equals(newName)) {
            throw new UniversityWarning(ErrorCodes.LECTURE_NAME_NULL);
        }
        if (course != null)
            course.haveDuplicates(newName);
        name = newName;
    }

    /**
     * Sets room for lecture and updates links with rooms
     * 
     * @param room
     *            new room, <b>null</b> unassigns room
     * @throws UniversityWarning
     *             ErrorCodes.ROOM_IS_TOO_SMALL if {@link #checkRoomSize(Room)}
     *             or {@link #checkRoomType(Room, LectureType)} failed
     */
    public void setRoom(Room room) throws UniversityWarning {
        if (course == null && checkRoomType(room, lectureType)) {
            appointedRoom = room;
        } 
        else if (room == null) {
            if (appointedRoom != null)
                appointedRoom.removeLecture(this);
            appointedRoom = room;
        } 
        else if (checkRoomSize(room) && checkRoomType(room, null)) {
            if (appointedRoom != null)
                appointedRoom.removeLecture(this);
            room.addLecture(this);
            appointedRoom = room;
        } 
        else {
            throw new UniversityWarning(ErrorCodes.ROOM_IS_TOO_SMALL);
        }
    }

    /**
     * Sets new type for the lecture if {@link #setRoom(Room) appointed} room is
     * relevant<br>
     * Checks relevance by calling {@link #checkRoomType(Room, LectureType)}
     * 
     * @param lectureType2
     *            - new type of lecture
     * @throws UniversityWarning
     *             when room is not valid for the type
     */
    public void setType(LectureType lectureType2) throws UniversityWarning {
        if (checkRoomType(appointedRoom, lectureType2)) {
            lectureType = lectureType2;
        } else {
            throw new UniversityWarning(ErrorCodes.ROOM_IS_TOO_SMALL);
        }

    }

    /**
     * Removes links to the Lecture. <br>
     * Used in {@link Course#removeLecture(Lecture)}<br>
     * Clears links to appointed room<br>
     * Calls {@link Room#removeLecture(Lecture)}
     */
    protected void removeLinks() {
        if (appointedRoom != null) {
            appointedRoom.removeLecture(this);
            appointedRoom = null;
        }
        course = null;
    }

    /**
     * Sets {@link Course} link for the lecture.<br>
     * Once set, course may not be changed.<br>
     * Called from
     * {@link Course#addLecture(String, Lecture.LectureType, String)}
     * 
     * @param c
     *            {@link Course}
     * @throws UniversityWarning
     *             when name is not unique for the course or appointed room is
     *             too small
     */
    protected void setCourse(final Course c) throws UniversityWarning {
        if (course != null && !course.equals(c)) {
            throw new UniversityWarning(ErrorCodes.LECTURE_COULD_NOT_BE_MOVED);
        }
        if (null != name
                && !Const.STRING_EMPTY.equals(name)
                && c != null
                && !c.equals(course)) {
            c.haveDuplicates(name);
        }
        if (availablePlaces() < c.countStudents()) {
            throw new UniversityWarning(ErrorCodes.ROOM_IS_TOO_SMALL);
        }
        course = c;
    }

    public Room getRoom() {
        return appointedRoom;
    }

}
